import logging
import os
import threading
import time
import traceback
import numpy as np
import pandas as pd
from api import models, utils
from api.serial import figure
from api.serial.data import SocketResponse, UIResult
from api.serial.detect import detect_sample, save_detect_result
from api.serial.jisuan_shangxiajie import jisuan_shangxiajie
from api.serial.rebuild_onehour import rebuild_onehour

from detecter import settings

logger = logging.getLogger('serial.consumer')

set_yichang_time = 3600


class SampleConsumer(threading.Thread):
    __instance = None

    def __init__(self, current_mould):
        super().__init__()
        self.setName('consumer')
        self.running = threading.Event()  # 用于暂停线程的标识
        self.keep_running = False
        self.current_mould = current_mould
        self.time_yichang = ''
        self.is_yichang = True
        self.sample_list = []

    @classmethod
    def new_instance(cls, current_mould):
        cls.__instance = SampleConsumer(current_mould)
        return cls.__instance

    @classmethod
    def get_instance(cls):
        return cls.__instance

    def is_consumer_running(self):
        return self.running.is_set()

    def begin(self):
        if not self.keep_running:
            self.keep_running = True
            self.running.set()
            self.setDaemon(False)  # 不设置守护线程, 就算websocket挂了也继续收集数据,处理数据, 存入临时文件中, 直到停止本工单才算停止此线程
            self.start()

    def stop(self):
        self.running.clear()  # 设置为False
        logger.warning('consumer线程阻塞')

    # 当前的正样本画像,每生产50个样本更新一次，使用最新的1000个样本进行更新
    def run(self):
        while self.keep_running:
            current_mould = self.current_mould
            portrait_result = current_mould.portrait_result
            # 样本列表
            self.sample_list = current_mould.producer.get_sample_list()
            # 返回结果
            ui = UIResult(0, '')
            # 负样本画像上次的更新信息字典 zxy 修改于 20200109
            neg_figure_info = {}
            if not self.running.is_set():
                # 停止按钮点击后的收尾工作
                sample_list_len = len(self.sample_list)
                if sample_list_len > 0:
                    for sample in self.sample_list:
                        utils.undetect_handler(current_mould, sample, current_mould.undetected_file)
                # 同步正样本画像数据到prod_order中来
                current_mould.prod_mould.state = 0
                current_mould.prod_mould.save(force_update=True)
                current_mould.is_socket_connected = False
                logger.info('样本建模与检测线程consumer阻塞！！！')
                self.running.wait()
            while self.running.is_set() and len(self.sample_list) > 0:
                logger.info('consumer线程运行中！！！')
                current_sample = self.sample_list.pop(0)  # 这里线程不安全
                # made_no与mould_code同义
                ui.made_no = current_mould.mould_code
                ui.sample_id = current_sample.id
                sample_id = current_sample.id
                setting_pressure = current_sample.setting_pressure
                setting_speed = current_sample.setting_speed

                # zxy 修改于 20200109
                db_mould = models.Mould.objects.get(mould_code=current_mould.mould_code)
                ui.current_threshold = db_mould.threshold
                # zxy 修改于 20200207
                ui.min_samples_to_cluster = settings.MIN_SAMPLES_TO_CLUSTER

                neg_count_all = db_mould.neg_count
                neg_count_inFigure = 0
                neg_figure_labelList = models.NegFigure.objects.filter(mould=db_mould)

                for neg_figure_label in neg_figure_labelList:
                    current_label_id = neg_figure_label.label_id  #
                    current_label = models.Label.objects.get(id=current_label_id).label_name
                    for i in range(3, 23):
                        count_reason = neg_figure_label.get_count(i)
                        current_reason = models.Reason.objects.get(id=i).reason_name
                        # logger.info(str(neg_figure_info))
                        try:
                            # 上次更新画像时的负样本数量
                            last_count = neg_figure_info[current_label][current_reason]
                        except KeyError:
                            if current_label not in neg_figure_info.keys():
                                neg_figure_info.update({current_label: {}})
                            neg_figure_info.get(current_label).update({current_reason: 0})
                            last_count = 0
                        # logger.info(f'reason: {current_reason},last_count: {neg_figure_info[current_label][
                        # current_reason]}')
                        if count_reason >= settings.NEGATIVE_START and count_reason != last_count:
                            # 第一次构建画像
                            neg_figure_info.get(current_label).update({current_reason: count_reason})
                            # logger.info("info: "+str(neg_figure_info[current_label][current_reason]))
                            # logger.info(str(neg_figure_info))
                            logger.info(f'标签{current_label}下的异常原因为{current_reason}的负样本的数量已到达'
                                        f'{settings.NEGATIVE_START}，开始进行建模')
                            file_path = os.path.join(settings.BASE_DIR, 'data', 'sample', current_mould.mould_code,
                                                     "负样本信息", current_label)
                            file_name = os.path.join(file_path, str(current_reason + ".csv"))
                            if not os.path.exists(file_path):
                                os.makedirs(file_path)
                            if not os.path.exists(file_name):
                                with open(file_name, mode='w') as fp:
                                    fp.close()
                            try:
                                portrait_temp = figure.FigureResult(None)
                                portrait_temp.build_portrait(file_name)
                                neg_figure_label.save_figure_result(portrait_temp.dump_to_db(), i)
                                # 如果异常就用原来的正样本画像
                            except Exception as e:
                                current_mould.websocket.msg_send(
                                    SocketResponse.error(f'标签{current_label}下的异常原因为{current_reason}的负样本'
                                                         f'第一次建模失败, {e}'))
                                continue
                        elif settings.NEGATIVE_START < count_reason != last_count \
                                and count_reason % settings.NEGATIVE_UPDATE == 0:
                            # 更新画像
                            neg_figure_info.get(current_label).update({current_reason: count_reason})
                            logger.info(f'标签{current_label}下的异常原因为{current_reason}的负样本的数量已到达'
                                        f'{count_reason}，更新建模')
                            file_path = os.path.join(settings.BASE_DIR, 'data', 'sample', current_mould.mould_code,
                                                     "负样本信息", current_label)
                            file_name = os.path.join(file_path, str(current_reason + ".csv"))
                            if not os.path.exists(file_path):
                                os.makedirs(file_path)
                            if not os.path.exists(file_name):
                                with open(file_name, mode='w') as fp:
                                    fp.close()
                            try:
                                # 建模
                                portrait_temp = figure.FigureResult(None)
                                portrait_temp.build_portrait(file_name)
                                neg_figure_label.save_figure_result(portrait_temp.dump_to_db(), i)
                                # 如果异常就用原来的画像
                            except Exception as e:
                                current_mould.websocket.msg_send(
                                    SocketResponse.error(f'标签{current_label}下的异常原因为{current_reason}的负样本'
                                                         f'建模失败, {e}'))
                                continue
                        neg_count_inFigure += count_reason
                ui.unassigned_count = neg_count_all - neg_count_inFigure

                if not self.running.is_set():  # 这里有bug ,应该把not去掉
                    logger.info(f'正在处理编号为 {sample_id} 的样本...')
                    current_mould.websocket.msg_send(SocketResponse.ok(f'正在处理编号为 {sample_id} 的样本...'))
                # 小于51,存完就退出
                if sample_id < settings.POSITIVE_START + 1:
                    logger.info(f'id为{sample_id} 小于{settings.POSITIVE_START + 1}, 存进正样本文件中')
                    utils.accepted_handler(current_mould, None, current_sample,
                                           current_mould.good_file)  # 前50模产品默认正常，detect_result的输入为空
                    # # 测试的时候用的
                    # try:
                    #     b1, a1, bb1, aa1 = jisuan_shangxiajie(portrait_result, current_mould)
                    #     ui.set_shangxiajie_pressure1(b1, a1)
                    #     ui.set_shangxiajie_speed(bb1, aa1)
                    # except:
                    #     logger.info('上下界线—————建模失败')

                    ui.set_setting_pressure(setting_pressure)
                    ui.set_setting_speed(setting_speed)
                    current_mould.websocket.msg_send(SocketResponse.ok(ui.get_info()))
                # 等于51先存,再建模,最后返回建模结果
                elif sample_id == settings.POSITIVE_START + 1:
                    logger.info(f'id 等于{settings.POSITIVE_START + 1}, 存入正样本文件后, 开始建模')
                    utils.accepted_handler(current_mould, None, current_sample, current_mould.good_file)
                    try:
                        # 建模  这里的建模就是更新的figure对象里面的各种参数。portrait_result就是figure对象
                        portrait_result.build_portrait(current_mould.good_file)
                        # 将正样本画像存进数据库中
                        current_mould.prod_mould.save_positive(portrait_result.dump_to_db())  # 建模成功以后，会调用dump_to_db这个函数，会更改is_result_safe的值为True
                    except Exception as e:
                        current_mould.websocket.msg_send(
                            SocketResponse.error(f'sample id 为{settings.POSITIVE_START}时建模失败,{e}'))
                        continue
                    try:
                        b1, a1, bb1, aa1 = jisuan_shangxiajie(portrait_result, current_mould)
                        ui.set_shangxiajie_pressure1(b1, a1)
                        ui.set_shangxiajie_speed(bb1, aa1)
                    except:
                        logger.info('上下界线—————建模失败')

                    ui.set_portrait_info(portrait_result.get_portrait_mean())
                    ui.set_seg_points(portrait_result.seg_points)
                    ui.set_setting_pressure(setting_pressure)
                    ui.set_setting_speed(setting_speed)
                    current_mould.websocket.msg_send(SocketResponse.ok(ui.get_info()))
                # 大于50,且是50的倍数时，更新正样本画像,再匹配
                elif sample_id > settings.POSITIVE_START + 1:
                    good_count = current_mould.prod_mould.get_pos_count()
                    sample_count = portrait_result.get_sample_count()  # 建模获取正样本文件中有多少样本数
                    if good_count % settings.POSITIVE_UPDATE == 0 and good_count != sample_count:
                        logger.info(f'开始构建正样本画像:第{good_count}个样品')
                        try:
                            portrait_result.build_portrait(current_mould.good_file)
                            current_mould.prod_mould.save_positive(portrait_result.dump_to_db())
                        except Exception as e:
                            # 如果失败了就恢复原来的正样本画像,之所以这么麻烦是因为样本建模的时候采用了一些甲方提出的硬编码参数，然而
                            # 机台生产出来的样本数据并不是完全一样
                            # portrait_result = current_mould.portrait_result = current_mould.prod_mould.get_positive()
                            # 如果建模异常就先用之前的正样本画像
                            portrait_result = current_mould.portrait_result
                            logger.error(f'{good_count} 构建正样本画像失败,{e}')
                            current_mould.websocket.msg_send(
                                SocketResponse.error(f'{e},正样本数为{good_count}时,更新正样本画像失败'))
                            pass
                        ui.set_portrait_info(portrait_result.get_portrait_mean())
                        ui.set_seg_points(portrait_result.seg_points)
                        ui.set_setting_pressure(setting_pressure)
                        ui.set_setting_speed(setting_speed)
                        current_mould.websocket.msg_send(SocketResponse.ok(ui.get_info()))
                    try:
                        portrait_seg_points = portrait_result.seg_points
                        sample_seg_points = current_sample.seg_points
                        # 模拟分段数据
                        ui.set_seg_points(portrait_seg_points)
                        # 样本分段数据
                        ui.set_seg_list(sample_seg_points)
                        logger.info(f"画像分段:{portrait_seg_points}, 样本:{sample_seg_points}")
                        if len(portrait_seg_points) != len(sample_seg_points):
                            logger.info("分段不匹配，请检查是否更换了模具或工单并重新首件")
                            utils.undetect_handler(current_mould, current_sample, current_mould.undetected_file)
                            current_mould.websocket.msg_send(SocketResponse.error(f"\n检测样品{current_sample.id}时出现分段不匹配, "
                                                                                  f"存入临时文件中, 参考分段:{portrait_seg_points}, "
                                                                                  f"实际分段:{sample_seg_points}, "
                                                                                  f"其中0为射胶开始点，其他为分段点"))
                            # 如果异常超过1小时，则自动建模
                            if self.is_yichang:
                                self.time_yichang = time.time()
                                self.is_yichang = False
                            if round(time.time() - self.time_yichang, 2) >= set_yichang_time:
                                # close_consumer(current_mould)
                                current_mould.websocket.msg_send(SocketResponse.error("重新建模中"))
                                rebuild_onehour(current_mould.mould_code)
                                # rebuild_onehour(current_mould.undetected_file,current_mould.mould_code)
                                # 建立连接
                                continue
                            continue
                        # 清空提示区
                        current_mould.websocket.msg_send(SocketResponse.error(" "))

                        if portrait_result.is_result_safe:
                            # 如果安全就进行检测
                            detect_result = detect_sample(current_sample, portrait_result, isPostive=True)  # 返回了一个列表，索引为5的是sigma倍数
                        else:
                            portrait_result = current_mould.prod_mould.get_positive()
                            raise Exception("正样本画像缺失关键数据")

                    except Exception as e:
                        utils.undetect_handler(current_mould, current_sample,
                                               current_mould.undetected_file)
                        logger.info(traceback.format_exc())

                        ui.set_portrait_info(portrait_result.get_portrait_mean())
                        ui.set_seg_points(portrait_result.seg_points)
                        ui.set_setting_pressure(setting_pressure)
                        ui.set_setting_speed(setting_speed)
                        current_mould.websocket.msg_send(SocketResponse.ok(ui.get_info()))

                        current_mould.websocket.msg_send(SocketResponse.error(f'{e},检测样品{current_sample.id}失败,存入临时文件中'))
                        # catch到异常就跳过这个样本

                        # 如果异常超过1小时，则自动建模
                        if self.is_yichang:
                            self.time_yichang = time.time()
                            self.is_yichang = False
                        if round(time.time() - self.time_yichang, 2) >= set_yichang_time:
                            # close_consumer(current_mould)
                            current_mould.websocket.msg_send(SocketResponse.error("重新建模中"))
                            rebuild_onehour(current_mould.mould_code)
                            # rebuild_onehour(current_mould.undetected_file,current_mould.mould_code)
                            continue
                        continue
                    # 从数据库中获取异常阈值, detected[5]是整个样本的异常率
                    if detect_result and detect_result[5] is not None:
                        ui.set_portrait_info(portrait_result.get_portrait_mean())
                        ui.set_seg_points(portrait_result.seg_points)
                        ui.set_setting_pressure(setting_pressure)
                        ui.set_setting_speed(setting_speed)
                        mould = models.Mould.objects.get(mould_code=current_mould.mould_code)
                        # sigma倍数表示
                        threshold = mould.threshold
                        logger.info(f"当前样本id:{sample_id} ,当前判断阈值:{threshold}")
                        logger.info(
                            f'times:{round(current_sample.total_rate, 2)},total_madis:{round(current_sample.total_madis, 2)},'
                            f'feat_dis:{round(portrait_result.dist_total_mean, 2)},dist_var:{round(portrait_result.dist_total_var, 2)}')

                        # 修改判断逻辑 zxy 修改于20200105
                        segRateErrorFlag = False
                        out_seg_times = 0
                        for segRate in detect_result[0]:
                            if segRate[2] > threshold:
                                segRateErrorFlag = True  # 如果变为true,就意味着是不良品
                                out_seg_times = segRate[2]
                                break
                        if detect_result[5] < threshold and not segRateErrorFlag:  # 如果是正常品质,存在指定文件中
                            self.is_yichang = True
                            ui.set_result(True)  # 品质正常
                            # 保留小数后两位
                            detect_result[5] = round(float(detect_result[5]), 2)
                            ui.set_detect_result(detect_result)
                            try:
                                b1, a1, bb1, aa1 = jisuan_shangxiajie(portrait_result, current_mould)
                                ui.set_shangxiajie_pressure1(b1, a1)
                                ui.set_shangxiajie_speed(bb1, aa1)
                            except:
                                logger.info('上下界线—————建模失败')

                            if current_mould.is_socket_connected:
                                current_mould.websocket.msg_send(SocketResponse.ok(ui.get_info()))
                            else:
                                save_detect_result(ui.get_info(), current_mould.unsend_file)
                            utils.accepted_handler(current_mould, detect_result, current_sample,
                                                   current_mould.good_file)
                        else:  # 如果是不良品,发布警告, 将数据存在另一文件中, 在数据库中添加记录

                            ui.set_result(False)  # 品质异常
                            ui.set_detect_result(detect_result)
                            try:
                                b1, a1, bb1, aa1 = jisuan_shangxiajie(portrait_result, current_mould)
                                ui.set_shangxiajie_pressure1(b1, a1)
                                ui.set_shangxiajie_speed(bb1, aa1)
                            except:
                                logger.info('上下界线—————建模失败')
                                # rebuild_onehour(current_mould.mould_code)

                            if current_mould.is_socket_connected:
                                current_mould.websocket.msg_send(SocketResponse.ok(ui.get_info()))
                            else:
                                save_detect_result(ui.get_info(), current_mould.unsend_file)
                            logger.info(f'seg_times: {round(out_seg_times, 2)}')
                            utils.rejects_handler(current_mould, detect_result, current_sample, current_mould.rejects_file,
                                                  current_mould.unassigned_file)
                            # 如果异常超过1小时，则自动建模
                            if self.is_yichang:
                                self.time_yichang = time.time()
                                self.is_yichang = False
                            if round(time.time() - self.time_yichang, 2) >= set_yichang_time:
                                # close_consumer(current_mould)
                                current_mould.websocket.msg_send(SocketResponse.error("重新建模中"))
                                rebuild_onehour(current_mould.mould_code)
                                continue
                    else:
                        logger.info(f'detect_result or detect_result[5] is None ==> {detect_result}')
                        utils.undetect_handler(current_mould, current_sample, current_mould.undetected_file)
                        # 如果异常超过1小时，则自动建模
                        if self.is_yichang:
                            self.time_yichang = time.time()
                            self.is_yichang = False
                        if round(time.time() - self.time_yichang, 2) >= set_yichang_time:
                            # close_consumer(current_mould)
                            current_mould.websocket.msg_send(SocketResponse.error("重新建模中"))
                            rebuild_onehour(current_mould.mould_code)
                            # rebuild_onehour(current_mould.undetected_file,current_mould.mould_code)
                            continue
